import cherrypy
import os
import pystache
import humanize
import json
import hashlib
import urllib.parse

from lxml.html import parse
from lxml.html import tostring
from io import StringIO

from polyglot import aggregator
from polyglot.backends.support import ResourceCategory, ResourcePlain, ResourceRawHTML, ResourceLink, Quiz, Forum, MultipleChoiceQuestion, FreeResponseQuestion, MultipartQuestion, FileUpload

import sys
backend = aggregator.load_backend(sys.argv[1])
config = {
    "Email": sys.argv[2],
    "Username": sys.argv[2],
    "Password": sys.argv[3],
    "School": sys.argv[4]
}

aggregator.authenticate(backend, config)

# TODO: FIXME
quizzes = {}
uploads = {}

def render_grade_summary(s):
    return str(s * 100) + "%" if s is not None else "N/A"

cor = backend.courses
courses = [{"uuid": c.id, "name": c.title, "code": c.teacher, "term": render_grade_summary(c.grade_summary)} for c in cor]

task_list = []

for c in cor:
    task_list += [{"title": task.name, "class": c.title, "due": humanize.naturaltime(task.due_date), "cid": c.id} for task in c.tasks]

def get_course(uuid):
    for c in cor:
        if c.id == uuid:
            return c

    return None

#TODO
def intl(text):
    return text

def svg_icon(text):
    if text[0] == ":":
        text = text[1:]

    if text.endswith("_link_icon"):
        text = text[0:-len("_link_icon")]

    # TODO: Don't be vulnerable FIXME
    with open("images/svg-icons/svg_icon_" + text + ".svg") as f:
        return f.read()

def template(templ, data):
    with open(templ, "r") as f:
        return pystache.render(f.read(), data)

def make_course_card(c):
    h = hashlib.md5(c["uuid"].encode("utf-8")).digest()
    color = "hsl(" + str(float(h[0])) + ", " + str(25 + (50*float(h[1])/256.0)) + "%, " + str(25 + (50*float(h[2])/256.0)) + "%)"

    return {
            "nickname": c["name"],
            "courseCode": c["code"],
            "originalName": c["name"],
            "term": c["term"],
            "href": "/courses?id=" + c["uuid"],
            "background": color,
            "backgroundColor": color,
            "links": [
                {"class": "announcements", "iconClass": "icon-announcement"},
                {"class": "assignments", "iconClass": "icon-assignment"},
                {"class": "discussions", "iconClass": "icon-discussion"},
            ]
    }

def make_dashboard(courses):
    return {
            "t": intl,
            "dashboard_courses": [make_course_card(c) for c in courses]
    }

def make_todo(assignments):
    return {
        "t": intl,
        "any_assignments": len(assignments) > 0,
        "assignments": [
            {**a, **({"classist": a["class"] is not None, "href": "/task?task=" + urllib.parse.quote(a["title"]) + "&course=" + a["cid"]}) } for a in assignments
        ]
}

data_todo = make_todo(task_list)

def outer_template(content, data_todo, groups, expand=False):
    data = {
            "t": intl,
            "svg_icon": svg_icon,
            "content": content,
            "groups": groups,
            "todo": data_todo,
            "expand": "ic-Layout-expand" if expand else ""
    }

    if len(groups) > 0:
        data["show-groups"] = True

    templated = template("views/layouts/application.mustache", data)

    # Right before it hits the browser, transform any emitted links
    # This will ensure the user does not accidentally click to e.g Google Docs

    tree = parse(StringIO(templated)).getroot()

    for link in tree.cssselect("a"):
        if "href" in link.attrib:
            link.attrib["href"] = aggregator.transform(link.attrib["href"])

    return tostring(tree).decode("utf-8")


def inner_template(t, d, data_todo, groups, expand=False):
    return outer_template(lambda x: template(t, d), data_todo, groups, expand)

def serialise_post(post, base):
    return {
            "canReply": False,
            "user": post.author,
            "message": post.body,
            "date": "TODO",
            "reply-href": base + "&reply=" + post.id,
            "replies": [serialise_post(r, base) for r in post.replies]
    }

def dump_resource(rsrc, expand, base):
    out = ""

    if isinstance(rsrc, ResourceCategory):
        out += "<h1>" + rsrc.name + "</h1><ul>"

        for child in rsrc.children:
            URL = base + "/" + urllib.parse.quote(child.name)

            out += "<li><a href='" + URL + "'>" + child.name + "</a></li>"

        out += "</ul>"
    elif isinstance(rsrc, ResourceRawHTML):
        out += "<h1>" + rsrc.name + "</h1>" + rsrc.html
    elif isinstance(rsrc, ResourcePlain):
        out += "<h1>" + rsrc.name + "</h1>" + rsrc.text
    elif isinstance(rsrc, ResourceLink):
        out += "<p><a href='" + rsrc.url + "'>" + rsrc.name + "</a></p>" 
    elif not expand and (isinstance(rsrc, Quiz) or isinstance(rsrc, Forum)):
        out += "<p><a href='" + base + "/" + rsrc.name + "&full=1'>" + rsrc.name + "</a></p>" 
    elif expand and isinstance(rsrc, Quiz):
        questions = rsrc.questions()
        print(questions)

        def format_part(q):
            if isinstance(q, MultipleChoiceQuestion):
                return {
                        "multiple_dropdowns_question": True,
                        "question": q.prompt,
                        "answers": [{"aid": 0, "text": text} for text in q.responses],
                        "parts": []
                }
            elif isinstance(q, FreeResponseQuestion):
                return {
                        "free_response": True,
                        "long": True, # TODO
                        "question": q.prompt,
                        "parts": [],
                }
            elif isinstance(q, MultipartQuestion):
                return {
                        "parts": [format_part(part) for part in q.parts]
                }

        data = {
                "t": intl,
                "title": rsrc.name,
                "id": rsrc.id,
                "action": "/submit_quiz",
                "description": None,
                "started_at": None,
                "previous_btn": False,
                "next_btn": False,
                "questions": [{"id": i, "name": intl("Question ") + str(i + 1), "question": format_part(q)} for i, q in enumerate(questions)]
        }

        quizzes[rsrc.id] = rsrc

        out += template("views/quizzes/quizzes/take_quiz.mustache", data)
    elif expand and isinstance(rsrc, Forum):
        data = serialise_post(rsrc.thread, base)
        data["t"] = intl
        if hasattr(rsrc, "title"):
            data["title"] = rsrc.title

        out += template("views/discussion_topics/show.mustache", data)
    elif isinstance(rsrc, FileUpload):
        uploads[rsrc.id] = rsrc

        data = {"id": rsrc.id, "name": rsrc.name}
        out += template("views/jst/re_upload_submissions_form.mustache", data)
    else:
        out += "<h1>" + rsrc.name + "</h1>Unknown element of type " + type(rsrc).__name__

    return out

resource_cache = {}

def path_serialise(path):
    return "/".join(map(str, path))

def find_resource(root, path, ref):
    if len(path) == 0:
        return root

    if len(ref) == 0:
        try:
            almost = resource_cache[path_serialise(path[:-1])]
            return find_resource(almost, [path[-1]], path[:-1])
        except KeyError:
            pass

    if isinstance(root, ResourceCategory):
        for child in root.children:
            if child.name == path[0]:
                rsrc = find_resource(child, path[1:], ref + [path[0]])
                resource_cache[path_serialise(ref + path)] = rsrc
                return rsrc

        print("Can't find " + path[0])
        return

    print("Wrong rsrc type with " + ",".join(path))

def find_task(lst, name):
    for task in lst:
        if task.name == name:
            return task

def find_thread(lst, id):
    for msg in lst:
        if msg.id == id:
            return msg

class PaletteServer(object):
    @cherrypy.expose
    def index(self):
        return self.dashboard()

    @cherrypy.expose
    def login(self):
        return inner_template("views/login/canvas/new_login_content.mustache", make_dashboard([]), [], [])

    @cherrypy.expose
    def do_login(self, email, password, school):
        print("Login ", email, password, school)
        return self.dashboard()

    @cherrypy.expose
    def submit_quiz(self, id, **kwargs):
        quiz = quizzes[id]
        questions = quiz.questions()
        print(id)
        print(kwargs)
        for name, value in kwargs.items():
            no = int(name[len("question_"):])
            q = questions[no]

            if isinstance(q, MultipleChoiceQuestion) or isinstance(q, FreeResponseQuestion):
                q.response = value
            elif isinstance(q, MultipartQuestion):
                for (part, response) in zip(q.parts, value):
                    part.response = response
            else:
                print("Unknown question type")
                print(q)

        quiz.submit(questions)

        return self.dashboard()

    @cherrypy.expose
    def submit_file(self, id, submission):
        uploads[id].upload(submission)
        return self.dashboard()

    @cherrypy.expose
    def dashboard(self):
        return inner_template("views/users/user_dashboard.mustache", make_dashboard(courses), data_todo, [])

    @cherrypy.expose
    def grades(self):
        data_all_grades = {
                "t": intl,
                "courses": [{"name": c.title, "grade": render_grade_summary(c.grade_summary) } for c in cor]
        }

        return inner_template("views/users/grades.mustache", data_all_grades, data_todo, [])

    @cherrypy.expose
    def conversations(self, submit=False, compose=False, id=None, subject="", to="", course="", body=""):
        inbox = backend.inbox

        if submit:
            inbox.send(to, subject, body)

        if not compose:
            msgs = inbox.threads

            summaries = [{
                "read": "read",
                "date": thread.messages[0].date,
                "participants": thread.messages[0].sender,
                "subject": thread.messages[0].subject,
                "message_count": 0,
                "id": thread.id,
                "summary": "" # TODO: Fix
            } for thread in msgs]

            thread = find_thread(msgs, id)

            replies = [{
                "avatar": None,
                "author": msg.sender,
                "date": msg.date,
                "body": msg.body # TODO sanitize
            } for msg in thread.messages] if thread is not None else []

            subject = thread.messages[0].subject if thread else ""

            data = {
                    "t": intl,
                    "messages": summaries,
                    "subject": subject,
                    "replies": replies,
                    "id": id
            }

            return inner_template("views/conversations/index_new.mustache", data, [], [], expand=True)
        else:
            data = {
                    "t": intl,
                    "id": id if id else "new"
            }

            return inner_template("views/jst/conversations/MessageFormDialog.mustache", data, [], [])

    @cherrypy.expose
    def task(self, course, task):
        t = find_task(get_course(course).tasks, task)
        o = "<h1>" + t.name + "</h1>" + "<p>" + humanize.naturaltime(t.due_date) + "</p>"
        rsrcs = t.resources
        if rsrcs is not None:
            for r in rsrcs:
                o += dump_resource(r, True, "/task?course=" + course + "&task=" + task)

        return outer_template(lambda _: o, data_todo, [])

    # Fetches an arbitrary URL in our context. Kind of a hack but shrug.

    @cherrypy.expose
    def wget(self, url):
        url = urllib.parse.unquote(url)
        print(url)

        # Set headers
        filename = urllib.parse.urlparse(url).path.split("/")[-1] 
        cherrypy.response.headers["Content-Type"] = "application/octet-stream"
        cherrypy.response.headers["Content-Disposition"] = 'attachment; filename="' + filename + '"'

        # Fetch and return
        u = backend.session.get(aggregator.transform(url))
        return u.content

    @cherrypy.expose
    def courses(self, id, active="grades", data="", full=False, reply=None, do=None, args=""):
        course = get_course(id)

        # Filter TODO list for course
        course_todo = make_todo([t for t in task_list if t["cid"] == course.id])

        groups = [
            {"type": "grades", "href": "/courses?id=" + id + "&active=grades", "name": "Grades", "show": True},
            {"type": "modules", "href": "/courses?id=" + id + "&active=modules", "name": "Modules", "show": True}
        ]

        for g in groups:
            if g["type"] == active:
                g["active"] = "active"

        if active == "grades":
            report = course.grades

            lst = []
            for category in report:
                for grade in category.grades:
                    grade = {
                            "unread": False,
                            "title": grade.name,
                            "due": "N/A",
                            "score": grade.grade,
                            "possible": grade.possible, 
                            "category": category.name,
                            "comment": grade.comment
                    }

                    lst += [grade]

            data_grades = {
                    "t": intl,
                    "grades": lst,
                    "total": render_grade_summary(course.grade_summary)
            }

            return inner_template("views/gradebooks/grade_summary.mustache", data_grades, course_todo, groups)
        elif active == "modules":
            path = urllib.parse.unquote(data).split("/")

            rsrcs = ResourceCategory("Root", course.resources)
            rsrc = find_resource(rsrcs, path[1:-1] if full else path[1:], [])

            # Don't show TODO on special resources

            if isinstance(rsrc, Forum) or isinstance(rsrc, Quiz):
                course_todo = []

            if full or (do == "reply" and not hasattr(rsrc, "thread")):
                contents = rsrc.children
                for con in contents:
                    if con.name == path[-1]:
                        rsrc = con
            if do == "reply":
                thread = rsrc.thread
                rsrc.reply(rsrc.posts[reply], args)
            elif reply is not None:
                form = {
                        "t": intl,
                        "id": id,
                        "active": "modules",
                        "data": data,
                        "action": "/courses",
                        "reply": reply
                }

                return inner_template("views/jst/discussions/reply_form.mustache", form, course_todo, groups)

            return outer_template(lambda _: dump_resource(rsrc, True, "/courses?id=" + id + "&active=modules&data=" + urllib.parse.quote(data)), course_todo, groups)
        else:
            return inner_template("views/error.mustache", {}, [], [])

conf = {
        '/': {
            'tools.staticdir.root': os.path.abspath(os.getcwd())
        },
        '/stylesheets': {
            'tools.staticdir.on': True,
            'tools.staticdir.dir': './stylesheets'
        }
}

cherrypy.quickstart(PaletteServer(), '/', conf)
